package com.superiron.scaffold.core.service.aspect;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName RedisLockAspect
 * @Author yzg
 * @Date 2022年6月13日14:32:37
 * @Description redis分布式锁拦截器实现
 **/
@Aspect
@Component
@Slf4j
public class RedisLockAspect {
	private static final Integer Max_RETRY_COUNT = 3;
	private static final String LOCK_PRE_FIX = "lockPreFix";
	private static final String LOCK_KEY = "lockKey";
	private static final String TIME_OUT = "timeOut";
	private static final int PROTECT_TIME = 2 << 11;//4096
	
	@Autowired
	private RedisTemplate redisTemplate;
	
	@Pointcut("@annotation(com.tsit.iot.core.common.aspect.RedisLock)")
	public void redisLockAspect() {
	}
	
	@Around("redisLockAspect()")
	public void lockAroundAction(ProceedingJoinPoint proceeding) throws Exception {
		
		//获取redis锁
		Boolean flag = this.getLock(proceeding, 0, System.currentTimeMillis());
		if (flag) {
			try {
				proceeding.proceed();
				Thread.sleep(PROTECT_TIME);
			} catch (Throwable throwable) {
				throw new RuntimeException("分布式锁执行发生异常" + throwable.getMessage(), throwable);
			} finally {        // 删除锁
				this.delLock(proceeding);
			}
		} else {
			log.info("其他系统正在执行此项任务");
		}
	}
	
	
	//获取锁
	private boolean getLock(ProceedingJoinPoint proceeding, int count, long currentTime) {
		//获取注解中的参数
		Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
		String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
		String key = (String) annotationArgs.get(LOCK_KEY);
		long expire = (long) annotationArgs.get(TIME_OUT);
		if (StringUtils.isEmpty(lockPrefix) || StringUtils.isEmpty(key)) {
			throw new RuntimeException("RedisLock,锁前缀，锁名未设置");
		}
		
		String redisLockKey = lockPrefix + ":" +  key;
		
		if (redisTemplate.opsForValue().setIfAbsent(redisLockKey, expire)) {
			return true;
		} else {
			//如果当前时间与锁的时间差，大于保护时间，则强制删除锁（防止死锁）
			long createTime = (long) redisTemplate.opsForValue().get(redisLockKey);
			if ((currentTime - createTime) > (expire * 1000 + PROTECT_TIME)) {
				count++;
				if (count > Max_RETRY_COUNT) {
					return false;
					
				}
				redisTemplate.delete(redisLockKey);
				getLock(proceeding, count, currentTime);
			}
			return false;
		}
		
	}
	
	/**
	 * 删除锁
	 */
	private void delLock(ProceedingJoinPoint proceeding) {
		Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
		String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
		String key = (String) annotationArgs.get(LOCK_KEY);
		
		String redisLockKey = lockPrefix + ":" +  key;
		
		redisTemplate.delete(redisLockKey);
	}
	
	/**
	 * 获取锁参数
	 *
	 * @param proceeding
	 * @return
	 */
	private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
		Class target = proceeding.getTarget().getClass();
		Method[] methods = target.getMethods();
		String methodName = proceeding.getSignature().getName();
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Map<String, Object> result = new HashMap<String, Object>();
				RedisLock redisLock = method.getAnnotation(RedisLock.class);
				result.put(LOCK_PRE_FIX, redisLock.lockPrefix());
				result.put(LOCK_KEY, redisLock.lockKey());
				result.put(TIME_OUT, redisLock.timeUnit().toSeconds(redisLock.timeOut()));
				return result;
			}
		}
		return null;
	}
	
	
}